﻿namespace JumpJump.Runtime
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;

    /// <summary>
    /// this class use for a single piece
    /// calculate the piece's neighbors
    /// and the position the piece can move to
    /// </summary>
    public class Piece
    {
        #region Fields

        /// <summary>
        /// the position in the chessboard
        /// </summary>
        private Coordinate _position;

        /// <summary>
        /// which user the piece belong to
        /// default = -1 
        /// </summary>
        private int _playerID = -1;

        /// <summary>
        /// the unique ID of this piece
        /// default = -1
        /// </summary>
        private int _pieceID = -1;

        /// <summary>
        /// the neighbor of six direction
        /// if not a valid neighbor, it's null
        /// </summary>
        private List<Coordinate> _neighbours = new List<Coordinate>(6);

        /// <summary>
        /// the position this piece can move to
        /// </summary>
        private List<Coordinate> _canMoveToPositionList = new List<Coordinate>();

        /// <summary>
        /// a matrix to store the floor of each BFS
        /// </summary>
        private int[,] _floorStep = new int[Chessboard.ChessboardWidth, Chessboard.ChessboardHeight];

        /// <summary>
        /// all the dependency coordinate that this piece may used to jump through
        /// </summary>
        private List<Coordinate> _dependencyCoordinateList = new List<Coordinate>();

        #endregion

        #region Constructor

        /// <summary>
        /// Initializes a new instance of the Piece class.
        /// constructor for input value
        /// </summary>
        /// <param name="uID">user ID</param>
        /// <param name="pID">piece ID</param>
        /// <param name="x">chessboard position X</param>
        /// <param name="y">chessboard position Y</param>
        public Piece(int uID, int pID, int x, int y)
        {
            this._playerID = uID;
            this._pieceID = pID;
            this._position = new Coordinate(x, y);
        }

        /// <summary>
        /// Initializes a new instance of the Piece class.
        /// default constructor
        /// </summary>
        public Piece()
        {
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets user ID 
        /// </summary>
        public int PlayerID
        {
            get
            {
                return this._playerID;
            }
        }

        /// <summary>
        /// Gets piece ID
        /// </summary>
        public int PieceID
        {
            get
            {
                return this._pieceID;
            }
        }

        /// <summary>
        /// Gets position of this Piece
        /// </summary>
        public Coordinate Position
        {
            get
            {
                return this._position;
            }
        }

        /// <summary>
        /// Gets the coordinate of this piece can move to
        /// </summary>
        public IList<Coordinate> CanMoveToPositionList
        {
            get
            {
                return this._canMoveToPositionList;
            }
        }

        /// <summary>
        /// Gets the coordinate of dependency
        /// </summary>
        public IList<Coordinate> DependencyCoordinateList
        {
            get
            {
                return this._dependencyCoordinateList;
            }
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// when piece move
        /// </summary>
        /// <param name="target">targe position</param>
        public void MoveTo(Coordinate target)
        {
            this._position = target;
        }

        /// <summary>
        /// get the next position that can move to
        /// </summary>
        /// <param name="chessboard">chessboard statue</param>
        public void GetNextPositions(int[,] chessboard)
        {
            // clear the list 
            this._canMoveToPositionList.Clear();

            // initial the floor matrix
            // start position is floor 0
            // position haven't be visited is -1
            // the floor value means the least step can jump to this position from start position
            int floorCount = 0;
            for (int i = 0; i < Chessboard.ChessboardWidth; i++)
            {
                for (int j = 0; j < Chessboard.ChessboardHeight; j++)
                {
                    this._floorStep[i, j] = -1;
                }
            }

            this._floorStep[this._position.X, this._position.Y] = 0;

            // move
            GetNeighbours();
            foreach (Coordinate coor in this._neighbours)
            {
                if (coor == null)
                {
                    continue;
                }
                else if (chessboard[coor.X, coor.Y] == Chessboard.EmptyPosition)
                {
                    this._canMoveToPositionList.Add(coor);
                    this._floorStep[coor.X, coor.Y] = floorCount + 1;
                }
            }

            // jump
            // initial the visited matrix
            // start position is true
            // others is false
            bool[,] visited = new bool[Chessboard.ChessboardWidth, Chessboard.ChessboardHeight];
            for (int i = 0; i < Chessboard.ChessboardWidth; i++)
            {
                for (int j = 0; j < Chessboard.ChessboardHeight; j++)
                {
                    visited[i, j] = false;
                }
            }

            visited[this._position.X, this._position.Y] = true;

            // initial the queue for BFS
            // when meet floorflag, floor value increase one
            Queue<Coordinate> queue = new Queue<Coordinate>();
            Coordinate floorFlag = new Coordinate(-2, -2);
            queue.Enqueue(this._position);
            queue.Enqueue(floorFlag);

            // BFS start
            while (queue.Count != 0)
            {
                Coordinate coor = queue.Peek();
                queue.Dequeue();

                // if the top item is floorflag
                if (coor.Equals(floorFlag))
                {
                    // value increase
                    floorCount++;

                    // if floorflag is the last item,
                    // means the last floor had been searched, break
                    if (queue.Count == 0)
                    {
                        break;
                    }
                    else
                    {
                        // a new floor is coming
                        queue.Enqueue(coor);
                        continue;
                    }
                }

                // search the six direction
                for (int i = 0; i < Coordinate.DirectionCount; i++)
                {
                    // get the througher position and validate it
                    int throughX = coor.X + Coordinate.Delta[i, 0];
                    int throughY = coor.Y + Coordinate.Delta[i, 1];
                    if (!Validator.ValidateThrougher(chessboard, throughX, throughY))
                    {
                        continue;
                    }

                    // if througher validate pass, try to jump through it
                    int targetX;
                    int targetY;
                    if (this.CanJumpTo(visited, chessboard, throughX, throughY, i, out targetX, out targetY))
                    {
                        // valid jump, creat a new coordinate and enqueue it.
                        // refresh the floor value of the target position
                        Coordinate oneNextPosition = new Coordinate(targetX, targetY);
                        queue.Enqueue(oneNextPosition);
                        visited[targetX, targetY] = true;
                        if (this._floorStep[targetX, targetY] == -1)
                        {
                            this._floorStep[targetX, targetY] = floorCount + 1;
                        }

                        // add into next position list
                        this._canMoveToPositionList.Add(oneNextPosition);

                        // add thougher into dependency list
                        this._dependencyCoordinateList.Add(new Coordinate(throughX, throughY));

                    }
                }
            }
        }

        /// <summary>
        /// get the piece neighbors in six direction
        /// </summary>
        public void GetNeighbours()
        {
            this._neighbours.Clear();
            foreach (Coordinate.Direction dir in Enum.GetValues(typeof(Coordinate.Direction)))
            {
                Coordinate oneNeighbour = this._position.GetNeighbour(dir);
                this._neighbours.Add(oneNeighbour);
            }
        }

        /// <summary>
        /// creat all valid target coordinate to movement list
        /// </summary>
        /// <returns>all valid movement</returns>
        public List<Movement> CreatAllMoveMent()
        {
            List<Movement> allMove = new List<Movement>();
            foreach (Coordinate coor in this._canMoveToPositionList)
            {
                Movement move = new Movement(_playerID, _pieceID, _position, coor);
                allMove.Add(move);
            }

            return allMove;
        }

        /// <summary>
        /// for a movement X to Y 
        /// get a path X a1 a2 ... an Y
        /// and n is the minimum number
        /// </summary>
        /// <param name="chessboard">chessboard statue</param>
        /// <param name="target">target position</param>
        /// <returns>a list that store coordinate of a1 a2 ... an</returns>
        public List<Coordinate> GetOneShortestPath(Chessboard chessboard, Coordinate target)
        {
            // clean the path list
            List<Coordinate> path = new List<Coordinate>();

            // if not a valid target position, do nothing but return
            if (!Validator.ValidateTargetPosition(this._canMoveToPositionList, target))
            {
                return path;
            }

            // if is a move not a jump 
            // add the target position directly and return
            if (target.X - this._position.X == 1 || target.Y - this._position.Y == 1)
            {
                path.Add(target);
                return path;
            }
            else
            {
                // if is a jump, calculate the shortest path
                // add the target position
                path.Add(target);

                // get the total step of this jump
                int totalStep = this._floorStep[target.X, target.Y];

                // using a converse way to get the path.
                // for all the position in _canMoveToPositionList 
                // exclude the the position that can move to not jump to.
                // they make up a connected graph.
                // so the shortest path must be n, n-1, n-2 ... 1
                while (totalStep != 1)
                {
                    foreach (Coordinate coor in this._canMoveToPositionList)
                    {
                        // calculate the througher k position,
                        // i jump to j through k
                        int througherX = (target.X + coor.X) / 2;
                        int througherY = (target.Y + coor.Y) / 2;

                        // if i can jump to j
                        // replace target as coor
                        if (this._floorStep[coor.X, coor.Y] == totalStep - 1
                            && Validator.ValidateThrougher(chessboard.Board, througherX, througherY, target.X, target.Y))
                        {
                            path.Add(coor);
                            totalStep--;
                            target = coor;
                        }
                    }
                }

                // reverse the path and return
                path.Reverse();
                return path;
            }
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// judge the whether the piece can jump to the position
        /// </summary>
        /// <param name="visited">matrix to judge whether the point had been visited</param>
        /// <param name="map">matrix for the current chessboard status</param>
        /// <param name="x">the X coordinate of a througher piece</param>
        /// <param name="y">the Y coordinate of a througher piece</param>
        /// <param name="dirID">direction ID</param>
        /// <param name="targetX">target position X</param>
        /// <param name="targetY">target position Y</param>
        /// <returns>true false</returns>
        private bool CanJumpTo(bool[,] visited, int[,] map, int x, int y, int dirID, out int targetX, out int targetY)
        {
            // get the target position
            targetX = x + Coordinate.Delta[dirID, 0];
            targetY = y + Coordinate.Delta[dirID, 1];

            if (!Validator.ValidatePosition(targetX, targetY))
            {
                return false;
            }

            // if target position had been visited, return false
            if (visited[targetX, targetY])
            {
                return false;
            }
            else if (map[targetX, targetY] != Chessboard.EmptyPosition)
            {
                // if target position is not empty, return false
                return false;
            }
            else
            {
                // else the target position is a valid position
                return true;
            }
        }

        #endregion
    }
}
